{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "c945f958",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Jupyter environment detected. Enabling Open3D WebVisualizer.\n",
      "[Open3D INFO] WebRTC GUI backend enabled.\n",
      "[Open3D INFO] WebRTCWindowSystem: HTTP handshake server disabled.\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "import open3d as o3d\n",
    "import json\n",
    "import math\n",
    "from copy import deepcopy"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "746eec0a",
   "metadata": {},
   "source": [
    "# 测试以及使用相关函数\n",
    "* 获取json数据"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "e295ab0f",
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_data(path):\n",
    "    #1.加载json数据\n",
    "    with open(path, 'r') as f:\n",
    "        data = json.load(f)\n",
    "    #2.注释转换格式\n",
    "    # 创建一个空的检测框列表\n",
    "    detection_boxes = []\n",
    "    # 遍历每个注释结果\n",
    "    for annotation in data: \n",
    "#         print('---')\n",
    "#         print(annotation)\n",
    "        # 提取注释中的相关信息\n",
    "        dimensions = annotation['3d_dimensions']\n",
    "        location = annotation['3d_location']\n",
    "        rotation = annotation['rotation']\n",
    "        # 计算边界框的坐标\n",
    "        h, w, l = dimensions['h'], dimensions['w'], dimensions['l']\n",
    "        x, y, z = location['x'], location['y'], location['z']\n",
    "        # 计算边界框的中心点坐标\n",
    "        center_x = x\n",
    "        center_y = y\n",
    "        center_z = z\n",
    "        # 计算旋转后的边界框顶点相对中心点的坐标（假设绕Z轴旋转）\n",
    "        theta = rotation  # 绕Z轴旋转角度\n",
    "        cos_theta = math.cos(theta)\n",
    "        sin_theta = math.sin(theta)\n",
    "        x_corners = [-l / 2, l / 2, l / 2, -l / 2]  # 边界框顶点相对中心点的x坐标\n",
    "        y_corners = [-w / 2, -w / 2, w / 2, w / 2]  # 边界框顶点相对中心点的y坐标\n",
    "        rotated_x_corners = [cos_theta * x_corner - sin_theta * y_corner for x_corner, y_corner in zip(x_corners, y_corners)]\n",
    "        rotated_y_corners = [sin_theta * x_corner + cos_theta * y_corner for x_corner, y_corner in zip(x_corners, y_corners)]\n",
    "\n",
    "        # 计算旋转后的边界框顶点坐标\n",
    "        rotated_x_min = min(rotated_x_corners)\n",
    "        rotated_y_min = min(rotated_y_corners)\n",
    "        rotated_x_max = max(rotated_x_corners)\n",
    "        rotated_y_max = max(rotated_y_corners)\n",
    "\n",
    "        # 计算旋转后的边界框前左上角和后右下角坐标\n",
    "        x_min = center_x + rotated_x_min\n",
    "        y_min = center_y + rotated_y_min\n",
    "        z_min = center_z - h / 2\n",
    "        x_max = center_x + rotated_x_max\n",
    "        y_max = center_y + rotated_y_max\n",
    "        z_max = center_z + h / 2\n",
    "        # 将边界框坐标添加到列表中\n",
    "        detection_boxes.append([x_min,y_min,z_min,x_max,y_max,z_max])\n",
    "    \n",
    "    #3.输出值\n",
    "    return data,detection_boxes"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7738ccd3",
   "metadata": {},
   "source": [
    "# 库函数\n",
    "* 去除检测框内物体\n",
    "* 去除地面分割结果\n",
    "* 地面点云分割"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6f5d237f",
   "metadata": {},
   "source": [
    "## 去除检测框内物体"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "35a2f8a9",
   "metadata": {},
   "outputs": [],
   "source": [
    "def removebbx(point_cloud,bbxpath):\n",
    "    # 加载目标检测的检测框结果\n",
    "    #detection_boxes = [[x1, y1, z1, x2, y2, z2], ...]  \n",
    "    data,detection_boxes=get_data(bbxpath)\n",
    "    # 将点云转换为numpy数组\n",
    "    points = np.asarray(point_cloud.points)\n",
    "    # 创建一个布尔掩码，标记要保留的点\n",
    "    mask = np.ones(len(points), dtype=bool)\n",
    "\n",
    "    # 遍历每个检测框\n",
    "    for box in detection_boxes:\n",
    "        # 提取检测框的边界坐标\n",
    "        print('++++++')\n",
    "        print(box)\n",
    "        x_min, y_min, z_min, x_max, y_max, z_max = box\n",
    "        \n",
    "        # 在边界框内部设置掩码为False\n",
    "        mask = np.logical_and(mask, np.logical_or(\n",
    "            np.logical_or(points[:, 0] < x_min, points[:, 0] > x_max),\n",
    "            np.logical_or(np.logical_or(points[:, 1] < y_min, points[:, 1] > y_max),\n",
    "                          np.logical_or(points[:, 2] < z_min, points[:, 2] > z_max))))\n",
    "    # 使用掩码来过滤点云\n",
    "    filtered_points = points[mask]\n",
    "\n",
    "    # 创建新的点云对象\n",
    "    filtered_point_cloud = o3d.geometry.PointCloud()\n",
    "    filtered_point_cloud.points = o3d.utility.Vector3dVector(filtered_points)\n",
    "\n",
    "    return filtered_point_cloud\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "de72381f",
   "metadata": {},
   "source": [
    "## 测试"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "ab5f8343",
   "metadata": {},
   "outputs": [],
   "source": [
    "#读取点云数据\n",
    "#data_path='../VIC/vehicle-side/cooperative-vehicle-infrastructure-vehicle-side-velodyne/002238.pcd'\n",
    "data_path='../VIC/vehicle-side/cooperative-vehicle-infrastructure-vehicle-side-velodyne/000000.pcd'\n",
    "\n",
    "point_data_raw=o3d.io.read_point_cloud(data_path)\n",
    "json_path='../VIC/vehicle-side/label/lidar/000000.json'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "8abc0323",
   "metadata": {},
   "outputs": [],
   "source": [
    "data,gt_box=get_data(json_path)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "9af152c1",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "++++++\n",
      "[63.31569161417295, 15.083309924759726, -1.4219149, 71.51950838582704, 18.255650075240272, 2.2790191]\n",
      "++++++\n",
      "[12.252499090867026, 2.296626965897879, -1.867328, 16.517640909132975, 4.235759034102121, -0.14788800000000002]\n",
      "++++++\n",
      "[19.900709960356124, 7.420639169276288, -1.9586085, 24.249970039643877, 9.412746830723714, -0.3995755000000001]\n",
      "++++++\n",
      "[91.00001895629961, 7.027560913312489, -1.3457222, 99.59400104370039, 9.772151086687511, 2.6848137999999997]\n",
      "++++++\n",
      "[51.984482577637024, 17.281956595323457, -1.7054918, 56.24165742236298, 19.238143404676542, -0.1998588]\n",
      "++++++\n",
      "[48.074002302627335, 9.045403630861212, -1.5850578, 56.26077769737266, 11.928116369138788, 1.9432842]\n",
      "++++++\n",
      "[67.84448207774021, 6.506236030298952, -1.4110667000000001, 72.11869792225978, 8.371089969701048, 0.2315663]\n",
      "++++++\n",
      "[90.4347426857627, 33.492762120227745, -1.3110731, 92.6853373142373, 37.97219787977226, 0.4388919]\n",
      "++++++\n",
      "[38.54750862154597, 0.18577786767381177, -1.6361338, 46.86421137845403, 3.855364132326188, 2.0947502]\n",
      "++++++\n",
      "[6.84668241691896, 6.727055307986255, -2.0202485, 11.42634158308104, 8.869752692013744, -0.5236514999999999]\n",
      "++++++\n",
      "[14.492656962498625, -1.7926198104340902, -1.8197834999999998, 19.346123037501375, 0.6228626104340901, -0.28155849999999993]\n",
      "++++++\n",
      "[25.638168618510935, 3.941119449492029, -1.8298416999999998, 29.905871381489067, 5.907910550507972, -0.1530857]\n",
      "++++++\n",
      "[36.453524077078, 15.78372121089808, -1.749854, 40.737175922922006, 17.66731878910192, -0.26127600000000006]\n",
      "++++++\n",
      "[21.05528069376998, 11.264895766779965, -2.0539235, 25.817939306230024, 13.485124233220034, -0.5103924999999999]\n",
      "++++++\n",
      "[38.361157192635716, 4.815924036753226, -1.768632, 42.64286280736428, 6.728707963246774, -0.250402]\n",
      "++++++\n",
      "[88.2048929501839, 11.142392585248542, -1.2425929999999998, 92.4475270498161, 13.10990741475146, 0.44043299999999996]\n",
      "++++++\n",
      "[23.661016453855535, 15.176407078977034, -2.0888825, 27.971643546144467, 17.094412921022965, -0.3515975]\n",
      "++++++\n",
      "[49.53426640475259, 13.725463772158458, -1.6541603, 53.81921359524741, 15.666116227841544, 0.05041169999999995]\n"
     ]
    }
   ],
   "source": [
    "#去除点云\n",
    "point2=removebbx(point_data_raw,json_path)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "f9348b9f",
   "metadata": {},
   "outputs": [],
   "source": [
    "#对比可视化结果\n",
    "#原始点云\n",
    "o3d.visualization.draw_geometries([point_data_raw],\n",
    "                                  zoom=0.7,\n",
    "                                  front=[0.5439, -0.2333, -0.8060],\n",
    "                                  lookat=[2.4615, 2.1331, 1.338],\n",
    "                                  up=[-0.1781, -0.9708, 0.1608])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e4085951",
   "metadata": {},
   "outputs": [],
   "source": [
    "#去掉物体后的点云\n",
    "o3d.visualization.draw_geometries([point2],\n",
    "                                  zoom=0.7,\n",
    "                                  front=[0.5439, -0.2333, -0.8060],\n",
    "                                  lookat=[2.4615, 2.1331, 1.338],\n",
    "                                  up=[-0.1781, -0.9708, 0.1608])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "14a45c78",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "PointCloud with 65991 points."
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "point_data_raw"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "3eb3b4a8",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "PointCloud with 62381 points."
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "point2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "1f11066b",
   "metadata": {},
   "outputs": [],
   "source": [
    "gt_box=[]\n",
    "for annotation in data:\n",
    "    dimensions = annotation['3d_dimensions']\n",
    "    location = annotation['3d_location']\n",
    "    rotation = annotation['rotation']\n",
    "    # 计算边界框的坐标\n",
    "    h, w, l = dimensions['h'], dimensions['w'], dimensions['l']\n",
    "    x, y, z = location['x'], location['y'], location['z']\n",
    "    gt_box.append([h,w,l,x,y,z,rotation])\n",
    "    \n",
    "for box in gt_box:\n",
    "    print(box)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "9c0fce4e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[1;33m[Open3D WARNING] invalid color in PaintUniformColor, clipping to [0, 1]\u001b[0;m\n",
      "\u001b[1;33m[Open3D WARNING] invalid color in PaintUniformColor, clipping to [0, 1]\u001b[0;m\n",
      "\u001b[1;33m[Open3D WARNING] invalid color in PaintUniformColor, clipping to [0, 1]\u001b[0;m\n",
      "\u001b[1;33m[Open3D WARNING] invalid color in PaintUniformColor, clipping to [0, 1]\u001b[0;m\n",
      "\u001b[1;33m[Open3D WARNING] invalid color in PaintUniformColor, clipping to [0, 1]\u001b[0;m\n",
      "\u001b[1;33m[Open3D WARNING] invalid color in PaintUniformColor, clipping to [0, 1]\u001b[0;m\n",
      "\u001b[1;33m[Open3D WARNING] invalid color in PaintUniformColor, clipping to [0, 1]\u001b[0;m\n",
      "\u001b[1;33m[Open3D WARNING] invalid color in PaintUniformColor, clipping to [0, 1]\u001b[0;m\n",
      "\u001b[1;33m[Open3D WARNING] invalid color in PaintUniformColor, clipping to [0, 1]\u001b[0;m\n",
      "\u001b[1;33m[Open3D WARNING] invalid color in PaintUniformColor, clipping to [0, 1]\u001b[0;m\n",
      "\u001b[1;33m[Open3D WARNING] invalid color in PaintUniformColor, clipping to [0, 1]\u001b[0;m\n",
      "\u001b[1;33m[Open3D WARNING] invalid color in PaintUniformColor, clipping to [0, 1]\u001b[0;m\n",
      "\u001b[1;33m[Open3D WARNING] invalid color in PaintUniformColor, clipping to [0, 1]\u001b[0;m\n",
      "\u001b[1;33m[Open3D WARNING] invalid color in PaintUniformColor, clipping to [0, 1]\u001b[0;m\n",
      "\u001b[1;33m[Open3D WARNING] invalid color in PaintUniformColor, clipping to [0, 1]\u001b[0;m\n",
      "\u001b[1;33m[Open3D WARNING] invalid color in PaintUniformColor, clipping to [0, 1]\u001b[0;m\n",
      "\u001b[1;33m[Open3D WARNING] invalid color in PaintUniformColor, clipping to [0, 1]\u001b[0;m\n",
      "\u001b[1;33m[Open3D WARNING] invalid color in PaintUniformColor, clipping to [0, 1]\u001b[0;m\n"
     ]
    }
   ],
   "source": [
    "vis = o3d.visualization.Visualizer()\n",
    "vis.create_window()\n",
    "vis.add_geometry(point_data_raw)\n",
    "#vis.add_geometry(point2)\n",
    "#gt_boxes = np.concatenate([gt_box_center, gt_box_size, gt_box_angles], axis=-1)\n",
    "for i, box in enumerate(gt_box):\n",
    "    b = o3d.geometry.OrientedBoundingBox()\n",
    "    b.center = box[3:6]\n",
    "    #lwh\n",
    "    h, w, l=box[0:3]\n",
    "    b.extent = [l,w,h]\n",
    "    \n",
    "    # with heading\n",
    "    R = o3d.geometry.OrientedBoundingBox.get_rotation_matrix_from_xyz((0, 0, box[6]))\n",
    "    b.rotate(R, b.center)\n",
    "    b.color = (255,0,0)\n",
    "    vis.add_geometry(b)\n",
    "\n",
    "# vis.get_render_option().background_color = np.asarray([0, 0, 0]) # 设置一些渲染属性\n",
    "vis.run()\n",
    "vis.destroy_window()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "66ac002a",
   "metadata": {},
   "source": [
    "## Ref\n",
    "[1] [open3d可视化](https://zhuanlan.zhihu.com/p/483414760)\n",
    "\n",
    "[2] [open3d可视化](https://blog.csdn.net/weixin_44003119/article/details/129826726)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "04e8beb6",
   "metadata": {},
   "source": [
    "## 地面点云分割"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "72465d97",
   "metadata": {},
   "outputs": [],
   "source": [
    "#原始方法分割\n",
    "def removeseg(point_cloud):\n",
    "    #1.进行地面分割\n",
    "        #1.1分割\n",
    "    plane_model, inliers = point_cloud.segment_plane(distance_threshold=0.1, ransac_n=3, num_iterations=1000)\n",
    "        #1.2过滤出地面点云\n",
    "    inlier_cloud = point_cloud.select_by_index(inliers)\n",
    "        #1.3过滤出非地面点云\n",
    "    outlier_cloud = point_cloud.select_by_index(inliers, invert=True)\n",
    "    #2.计算地面法向量\n",
    "    ground_normal = plane_model[0:3]\n",
    "    #3.输出 地面、非地面、法向量\n",
    "    return inlier_cloud,outlier_cloud,ground_normal"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c714c3f3",
   "metadata": {},
   "source": [
    "## 测试"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "f963e932",
   "metadata": {},
   "outputs": [],
   "source": [
    "#读取点云数据\n",
    "#data_path='../VIC/vehicle-side/cooperative-vehicle-infrastructure-vehicle-side-velodyne/002238.pcd'\n",
    "data_path='../VIC/vehicle-side/cooperative-vehicle-infrastructure-vehicle-side-velodyne/000000.pcd'\n",
    "point_data_raw=o3d.io.read_point_cloud(data_path)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "9469ed16",
   "metadata": {},
   "outputs": [],
   "source": [
    "ground_cloud,non_ground,ground_normal=removeseg(point_data_raw)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "a8786be3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "PointCloud with 15480 points.\n",
      "PointCloud with 50511 points.\n",
      "PointCloud with 65991 points.\n",
      "[-0.00872024  0.02639497  0.99961356]\n"
     ]
    }
   ],
   "source": [
    "print(ground_cloud)\n",
    "print(non_ground)\n",
    "print(point_data_raw)\n",
    "print(ground_normal)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "f9a0af86",
   "metadata": {},
   "outputs": [],
   "source": [
    "o3d.visualization.draw_geometries([ground_cloud])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "3e1bce5c",
   "metadata": {},
   "outputs": [],
   "source": [
    "o3d.visualization.draw_geometries([non_ground])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "1143f3b4",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "PointCloud with 14877 points."
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ground_cloud"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3dcaa2fb",
   "metadata": {},
   "source": [
    "### 法向量求解"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1d931d18",
   "metadata": {},
   "outputs": [],
   "source": [
    "#1.通过内置方法，再次求法向量\n",
    "# radius = 0.01  # 搜索半径\n",
    "# max_nn = 30  # 邻域内用于估算法线的最大点数\n",
    "# ground_cloud.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius, max_nn))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "id": "4763f13d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[1;33m[Open3D WARNING] invalid color in PaintUniformColor, clipping to [0, 1]\u001b[0;m\n"
     ]
    }
   ],
   "source": [
    "#2.使用分割时求的法向量\n",
    "\n",
    "# 创建法向量箭头\n",
    "arrow = o3d.geometry.TriangleMesh.create_arrow(cylinder_radius=0.1, cone_radius=0.2, cylinder_height=1, cone_height=2)\n",
    "arrow.paint_uniform_color([255, 0, 0])  # 设置箭头颜色为红色\n",
    "\n",
    "arrow_cloud = o3d.geometry.PointCloud()\n",
    "arrow_cloud.points = o3d.utility.Vector3dVector(arrow.vertices)\n",
    "\n",
    "# 创建法向量箭头的变换矩阵\n",
    "T = np.eye(4)\n",
    "T[:3, 3] = ground_cloud.points[100]  # 设置箭头的位置为地面点云的第一个点\n",
    "rotation = o3d.geometry.get_rotation_matrix_from_xyz((0, 0, np.arctan2(ground_normal[1], ground_normal[0])))\n",
    "T[:3, :3] = rotation\n",
    "\n",
    "# 应用变换矩阵\n",
    "#arrow.transform(np.dot(T[:3,3],rotation))\n",
    "arrow.transform(T)\n",
    "#arrow_cloud.transform(np.matmul(T, rotation))\n",
    "\n",
    "# 可视化地面点云和法向量箭头\n",
    "o3d.visualization.draw_geometries([ground_cloud, arrow])\n",
    "#o3d.visualization.draw_geometries([ground_cloud])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "2b04d4a0",
   "metadata": {},
   "outputs": [],
   "source": [
    "#3.赋值求解\n",
    "ground_normal.shape\n",
    "result = np.tile(ground_normal, (len(ground_cloud.points), 1))\n",
    "ground_cloud.normals=o3d.utility.Vector3dVector(result)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a3d55022",
   "metadata": {},
   "source": [
    "### 结果可视化"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "36b09351",
   "metadata": {},
   "outputs": [],
   "source": [
    "o3d.visualization.draw_geometries([point_data_raw],\n",
    "                                  zoom=0.7,\n",
    "                                  front=[0.5439, -0.2333, -0.8060],\n",
    "                                  lookat=[2.4615, 2.1331, 1.338],\n",
    "                                  up=[-0.1781, -0.9708, 0.1608])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "ebb38f1e",
   "metadata": {},
   "outputs": [],
   "source": [
    "o3d.visualization.draw_geometries([ground_cloud],\n",
    "                                  point_show_normal=True,\n",
    "                                  zoom=0.7,\n",
    "                                  front=[0.5439, -0.2333, -0.8060],\n",
    "                                  lookat=[2.4615, 2.1331, 1.338],\n",
    "                                  up=[-0.1781, -0.9708, 0.1608])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "6ee09069",
   "metadata": {},
   "outputs": [],
   "source": [
    "o3d.visualization.draw_geometries([non_ground],\n",
    "                                  zoom=0.7,\n",
    "                                  front=[0.5439, -0.2333, -0.8060],\n",
    "                                  lookat=[2.4615, 2.1331, 1.338],\n",
    "                                  up=[-0.1781, -0.9708, 0.1608])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "984ca852",
   "metadata": {},
   "source": [
    "## Ref\n",
    "\n",
    "[1] [open3d创建圆锥](https://blog.csdn.net/QLeelq/article/details/124003041)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.16"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
